/*******************************************************************************
 * Copyright (c) 2010-2011 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.lib;



import java.io.InputStream;

import org.luaj.Globals;
import org.luaj.Lua;
import org.luaj.LuaClosure;
import org.luaj.LuaError;
import org.luaj.LuaFunction;
import org.luaj.LuaString;
import org.luaj.LuaTable;
import org.luaj.LuaValue;
import org.luaj.Varargs;
import org.luaj.lib.jse.LuajavaLib;

/**
 * Subclass of {@link LibFunction} which implements the lua standard package and module
 * library functions.
 *
 * <h3>Lua Environment Variables</h3>
 * The following variables are available to lua scrips when this library has been loaded:
 * <ul>
 * <li><code>"package.loaded"</code> Lua table of loaded modules.
 * <li><code>"package.path"</code> Search path for lua scripts.
 * <li><code>"package.preload"</code> Lua table of uninitialized preload functions.
 * <li><code>"package.searchers"</code> Lua table of functions that search for object to load.
 * </ul>
 *
 * <h3>Java Environment Variables</h3>
 * These Java environment variables affect the library behavior:
 * <ul>
 * <li><code>"luaj.package.path"</code> Initial value for <code>"package.path"</code>.  Default value is <code>"?.lua"</code>
 * </ul>
 *
 * <h3>Loading</h3>
 * Typically, this library is included as part of a call to either
 *
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * System.out.println( globals.get("require").call"foo") );
 * } </pre>
 * <p>
 * To instantiate and use it directly,
 * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as:
 * <pre> {@code
 * Globals globals = new Globals();
 * globals.load(new JseBaseLib());
 * globals.load(new PackageLib());
 * System.out.println( globals.get("require").call("foo") );
 * } </pre>
 * <h3>Limitations</h3>
 * This library has been implemented to match as closely as possible the behavior in the corresponding library in C.
 * However, the default filesystem search semantics are different and delegated to the bas library
 * as outlined in the {@link BaseLib} and {@link org.luaj.lib.jse.JseBaseLib} documentation.
 * <p>
 *
 * @see LibFunction
 * @see BaseLib
 * @see org.luaj.lib.jse.JseBaseLib
 * @see org.luaj.lib.jse.JsePlatform
 * @see <a href="http://www.lua.org/manual/5.2/manual.html#6.3">Lua 5.2 Package Lib Reference</a>
 */
public class PackageLib extends TwoArgFunction {

    /**
     * The default value to use for package.path.  This can be set with the system property
     * <code>"luaj.package.path"</code>, and is <code>"?.lua"</code> by default.
     */
    public static final String DEFAULT_LUA_PATH;

    static {
        String path = null;
        try {
            path = System.getProperty("luaj.package.path");
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        if (path == null) {
            path = "?.lua";
        }
        DEFAULT_LUA_PATH = path;
    }

    private static final LuaString _T = valueOf("_T");
    private static final LuaString _M = valueOf("_M");
    private static final LuaString _NAME = valueOf("_NAME");
    private static final LuaString _PACKAGE = valueOf("_PACKAGE");
    private static final LuaString _DOT = valueOf(".");
    private static final LuaString _MODULES = valueOf("modules");

    static final LuaString _LOADED = valueOf("loaded");
    private static final LuaString _LOADLIB = valueOf("loadlib");
    static final LuaString _PRELOAD = valueOf("preload");
    static final LuaString _PATH = valueOf("path");
    static final LuaString _SEARCHPATH = valueOf("searchpath");
    static final LuaString _SEARCHERS = valueOf("searchers");

    /**
     * The globals that were used to load this library.
     */
    Globals globals;

    /**
     * The table for this package.
     */
    LuaTable package_;

    /**
     * Loader that loads from {@code preload} table if found there
     */
    public preload_searcher preload_searcher;

    /**
     * Loader that loads as a lua script using the lua path currently in {@link _PATH}
     */
    public lua_searcher lua_searcher;

    /**
     * Loader that loads as a Java class.  Class must have public constructor and be a LuaValue.
     */
    public java_searcher java_searcher;
    public class_searcher class_searcher;
    public require require;
    public module module;

    private static final LuaString _SENTINEL = valueOf("\u0001");

    private static final String FILE_SEP = System.getProperty("file.separator");
    public LuaTable searchers;

    public PackageLib() {
    }

    /**
     * Perform one-time initialization on the library by adding package functions
     * to the supplied environment, and returning it as the return value.
     * It also creates the package.preload and package.loaded tables for use by
     * other libraries.
     *
     * @param modname the module name supplied if this is loaded via 'require'.
     * @param env     the environment to load into, typically a Globals instance.
     */
    public LuaValue call(LuaValue modname, LuaValue env) {
        globals = env.checkglobals();
        globals.set("require", require = new require());
        globals.set("module", module = new module());
        package_ = new LuaTable();
        package_.set(_LOADED, new LuaTable());
        package_.set(_MODULES, new LuaTable());
        package_.set(_PRELOAD, new LuaTable());
        package_.set(_PATH, LuaValue.valueOf(DEFAULT_LUA_PATH));
        package_.set(_LOADLIB, new loadlib());
        package_.set(_SEARCHPATH, new searchpath());
        searchers = new LuaTable();
        searchers.set(1, preload_searcher = new preload_searcher());
        searchers.set(2, lua_searcher = new lua_searcher());
        searchers.set(3, java_searcher = new java_searcher());
        searchers.set(4, class_searcher = new class_searcher());
        package_.set(_SEARCHERS, searchers);
        package_.get(_LOADED).set("package", package_);
        env.set("package", package_);
        globals.package_ = this;
        return env;
    }

    /**
     * Allow packages to mark themselves as loaded
     */
    public void setIsLoaded(String name, LuaTable value) {
        package_.get(_LOADED).set(name, value);
    }


    /**
     * Set the lua path used by this library instance to a new value.
     * Merely sets the value of {@link path} to be used in subsequent searches.
     */
    public void setLuaPath(String newLuaPath) {
        package_.set(_PATH, LuaValue.valueOf(newLuaPath));
    }

    public String tojstring() {
        return "package";
    }

    // ======================== Package loading =============================

    /**
     * require (modname)
     * <p>
     * Loads the given module. The function starts by looking into the package.loaded table
     * to determine whether modname is already loaded. If it is, then require returns the value
     * stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module.
     * <p>
     * To find a loader, require is guided by the package.searchers sequence.
     * By changing this sequence, we can change how require looks for a module.
     * The following explanation is based on the default configuration for package.searchers.
     * <p>
     * First require queries package.preload[modname]. If it has a value, this value
     * (which should be a function) is the loader. Otherwise require searches for a Lua loader using
     * the path stored in package.path. If that also fails, it searches for a Java loader using
     * the classpath, using the public default constructor, and casting the instance to LuaFunction.
     * <p>
     * Once a loader is found, require calls the loader with two arguments: modname and an extra value
     * dependent on how it got the loader. If the loader came from a file, this extra value is the file name.
     * If the loader is a Java instance of LuaFunction, this extra value is the environment.
     * If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname].
     * If the loader does not return a non-nil value and has not assigned any value to package.loaded[modname],
     * then require assigns true to this entry.
     * In any case, require returns the final value of package.loaded[modname].
     * <p>
     * If there is any error loading or running the module, or if it cannot find any loader for the module,
     * then require raises an error.
     */
    public final class require extends OneArgFunction {
        public LuaValue call(LuaValue arg) {
            LuaString name = arg.checkstring();
            LuaValue loaded = package_.get(_LOADED);
            LuaValue result = loaded.get(name);
            if (result.toboolean()) {
                if (result == _SENTINEL)
                    error("loop or previous error loading module '" + name + "'");
                return result;
            }

            /* else must load it; iterate over available loaders */
            LuaTable tbl = package_.get(_SEARCHERS).checktable();
            StringBuffer sb = new StringBuffer();
            Varargs loader = null;
            for (int i = 1; true; i++) {
                LuaValue searcher = tbl.get(i);
                if (searcher.isnil()) {
                    error("module '" + name + "' not found: " + name + sb);
                }

                /* call loader with module name as argument */
                loader = searcher.invoke(name);

                if (loader.isuserdata(1))
                    break;
                if (loader.isfunction(1))
                    break;
                if (loader.isstring(1))
                    sb.append(loader.tojstring(1));
            }
            // load the module using the loader
            loaded.set(name, _SENTINEL);
            if (loader.isuserdata(1))
                result = loader.arg1();
            else
                result = loader.arg1().call(name, loader.arg(2));
            if (!result.isnil())
                loaded.set(name, result);
            else if ((result = loaded.get(name)) == _SENTINEL)
                loaded.set(name, result = LuaValue.TRUE);
            return result;
        }
    }

    public static class loadlib extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            args.checkstring(1);
            return varargsOf(NIL, valueOf("dynamic libraries not enabled"), valueOf("absent"));
        }
    }

    public class preload_searcher extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            LuaString name = args.checkstring(1);
            LuaValue val = package_.get(_PRELOAD).get(name);
            return val.isnil() ?
                    valueOf("\n\tno field package.preload['" + name + "']") :
                    val;
        }
    }

    public class lua_searcher extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            LuaString name = args.checkstring(1);

            // get package path
            LuaValue path = package_.get(_PATH);
            if (!path.isstring())
                return valueOf("package.path is not a string");

            // get the searchpath function.
            Varargs v = package_.get(_SEARCHPATH).invoke(varargsOf(name, path));

            // Did we get a result?
            if (!v.isstring(1))
                return v.arg(2).tostring();
            LuaString filename = v.arg1().strvalue();

            // Try to load the file.
            v = globals.loadfile(filename.tojstring());

            if (v.arg1().isfunction())
                return LuaValue.varargsOf(v.arg1(), filename);

            // report error
            return varargsOf(NIL, valueOf("'" + filename + "': " + v.arg(2).tojstring()));
        }
    }

    public class searchpath extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            String name = args.checkjstring(1);
            String path = args.checkjstring(2);
            String sep = args.optjstring(3, ".");
            String rep = args.optjstring(4, FILE_SEP);

            // check the path elements
            int e = -1;
            int n = path.length();
            StringBuffer sb = null;
            name = name.replace(sep.charAt(0), rep.charAt(0));
            while (e < n) {

                // find next template
                int b = e + 1;
                e = path.indexOf(';', b);
                if (e < 0)
                    e = path.length();
                String template = path.substring(b, e);

                // create filename
                int q = template.indexOf('?');
                String filename = template;
                if (q >= 0) {
                    filename = template.substring(0, q) + name + template.substring(q + 1);
                }

                // try opening the file
                InputStream is = globals.finder.findResource(filename);
                if (is != null) {
                    try {
                        is.close();
                    } catch (java.io.IOException ioe) {
                    }
                    return valueOf(filename);
                }

                // report error
                if (sb == null)
                    sb = new StringBuffer();
                sb.append("\n\t").append(filename);
            }
            return varargsOf(NIL, valueOf(sb.toString()));
        }
    }

    public class java_searcher extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            String name = args.checkjstring(1);
            String classname = toClassname(name);
            Class c = null;
            LuaValue v = null;
            try {
                c = Class.forName(classname);
                v = (LuaValue) c.newInstance();
                if (v.isfunction())
                    ((LuaFunction) v).initupvalue1(globals);
                return varargsOf(v, globals);
            } catch (ClassNotFoundException cnfe) {
                return valueOf("\n\tno lua class '" + classname + "'");
            } catch (Exception e) {
                return valueOf("\n\tjava load failed on '" + classname + "', " + e);
            }
        }
    }

    public class class_searcher extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            String name = args.checkjstring(1);
            String classname = toClassname(name);
            try {
                return globals.luajavaLib.bindClassForName(classname);
            } catch (ClassNotFoundException cnfe) {
                return valueOf("\n\tno class '" + classname + "'");
            } catch (Exception e) {
                return valueOf("\n\tjava load failed on '" + classname + "', " + e);
            }
        }
    }

    /**
     * Convert lua filename to valid class name
     */
    public static String toClassname(String filename) {
        int n = filename.length();
        int j = n;
        if (filename.endsWith(".lua"))
            j -= 4;
        for (int k = 0; k < j; k++) {
            char c = filename.charAt(k);
            if ((!isClassnamePart(c)) || (c == '/') || (c == '\\')) {
                StringBuffer sb = new StringBuffer(j);
                for (int i = 0; i < j; i++) {
                    c = filename.charAt(i);
                    sb.append(
                            (isClassnamePart(c)) ? c :
                                    ((c == '/') || (c == '\\')) ? '.' : '_');
                }
                return sb.toString();
            }
        }
        return n == j ? filename : filename.substring(0, j);
    }

    private static boolean isClassnamePart(char c) {
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
            return true;
        switch (c) {
            case '.':
            case '$':
            case '_':
                return true;
            default:
                return false;
        }
    }

    public final class module extends VarArgFunction {

        public Varargs invoke(Varargs args) {
            LuaString modname = args.checkstring(1);
            int n = args.narg();
            LuaValue loaded = package_.get(_LOADED);
            LuaValue pkg = package_.get(_MODULES);
            LuaValue value = loaded.get(modname);
            LuaValue module;
            if (!value.istable()) { /* not found? */
                module = findtable(pkg, modname);
                loaded.set(modname, module);
            } else {
                module = value;
            }

            /* check whether table already has a _NAME field */
            LuaValue name = module.get(_NAME);
            if (name.isnil()) {
                modinit(module, modname);
            }

            // set the environment of the current function
            /*if (Lua.LUA_FUNC_ENV) {
                LuaFunction f = globals.running.callstack.getCallFrame(1).f;
                if (f == null)
                    error("no calling function");
                if (!f.isclosure())
                    error("'module' not called from a Lua function");
                f.setfenv(module);
            } else if (Lua.LUA_LOCAL_ENV) {
                DebugLib.CallFrame frame = globals.running.callstack.getCallFrame(1);
                if (frame == null)
                    return argerror(0, "'module' not called from a Lua function");
                frame.setLocal(1 + frame.f.checkclosure().p.numparams, module);
            } else {
                LuaClosure c = (LuaClosure) globals.running.callstack.getCallFrame(1).f;
                c.upValues[0].setValue(module);
            }*/
            // apply the functions
            for (int i = 2; i <= n; i++)
                args.arg(i).call(module);
            /*LuaTable t = new LuaTable();
            module.set(_T, t);
            LuaTable mt = new LuaTable();
            mt.set(LuaValue.INDEX,module);*/
            // returns no results
            return module;
        }

    }

    /**
     * @param table the table at which to start the search
     * @param fname the name to look up or create, such as "abc.def.ghi"
     * @return the table for that name, possible a new one, or null if a non-table has that name already.
     */
    private static LuaValue findtable(LuaValue table, LuaString fname) {
        int b, e = (-1);
        do {
            e = fname.indexOf(_DOT, b = e + 1);
            if (e < 0)
                e = fname.m_length;
            LuaString key = fname.substring(b, e);
            LuaValue val = table.rawget(key);
            if (val.isnil()) { /* no such field? */
                LuaTable field = new LuaTable(); /* new table for field */
                table.set(key, field);
                table = field;
            } else if (!val.istable()) {  /* field has a non-table value? */
                return null;
            } else {
                table = val;
            }
        } while (e < fname.m_length);
        return table;
    }

    private static void modinit(LuaValue module, LuaString modname) {
        /* module._M = module */
        LuaTable m = new LuaTable();
        m.set(LuaValue.INDEX, module);
        m.setmetatable(m);
        module.set(_M, m);
        int e = modname.lastIndexOf(_DOT);
        module.set(_NAME, modname);
        module.set(_PACKAGE, (e < 0 ? EMPTYSTRING : modname.substring(0, e + 1)));
        module.set(LuaValue.CALL, new modcall());
        /*module.set(LuaValue.TOSTRING, new OneArgFunction() {
            @Override
            public LuaValue call(LuaValue arg) {
                return LuaValue.valueOf("module:" + modname);
            }
        });*/
        module.setmetatable(module);
    }


    //mod by nirenr
    static class modcall extends VarArgFunction {
        @Override
        public Varargs invoke(Varargs args) {
            LuaValue module = args.arg1();
            args = args.subargs(2);
            LuaValue m = module.get(_M);
            LuaValue f = module.get("new");
            if(f.isnil())
                f=m.get("new");
            LuaValue mt = m.getmetatable();
            LuaTable th = new LuaTable();
            mt = mt.checktable().clone();
            mt.set(LuaValue.INDEX, m);
            th.setmetatable(mt);
            f.invoke(th, args);
            return th;
        }
    }
}
